﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GestureSaver;
using Microsoft.Kinect;
using System.IO;
using System.Globalization;

namespace Experimenter
{
    class Algorithm1 : IAlgorithm
    {
        GestureDB trainingDatabase;
        Dictionary<GestureType, Dictionary<JointType, double>> weights;
        double[] BETAS = { -10, 100 };
        
        public void Train(GestureDB trainDB)
        {
            this.trainingDatabase = trainDB;

            Common.CenterizeAllGestures(trainingDatabase);
            Common.NormalizeAllShoulderLengths(trainingDatabase);
            
            //double bestBeta = GetBestBeta();

            double bestBeta = -10;
            weights = GetWeights(bestBeta);         

            WriteWeightsToFile(weights, Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + @"\weightsAlg1.txt");
        }

        private double GetBestBeta()
        {
            double maxRatio = 0;
            double maxBeta = 0;
            
            Dictionary<GestureType, Dictionary<JointType, double>>[] tempWeights = new Dictionary<GestureType, Dictionary<JointType, double>>[BETAS.Length];

            for (int ii = 0; ii < BETAS.Length; ii++)
            {
                tempWeights[ii] = GetWeights(BETAS[ii]);

                double[,] R = new double[trainingDatabase.TotalClassCount, trainingDatabase.TotalClassCount];

                for (int p = 0; p < trainingDatabase.TotalClassCount; p++)
                {
                    for (int r = 0; r < trainingDatabase.TotalClassCount; r++)
                    {
                        double sum = 0;

                        for (int i = 0; i < trainingDatabase[p].Count; i++)
                        {
                            for (int j = 0; j < trainingDatabase[r].Count; j++)
                            {
                                Gesture g1 = trainingDatabase[p][i];
                                Gesture g2 = trainingDatabase[r][j];

                                double temp = Common.DTW(g1, g2, null, tempWeights[ii][trainingDatabase[p][0].gestureType]);

                                sum += temp;
                            }
                        }

                        R[p, r] = sum;
                    }
                }

                double ratio = GetVarianceRatio(R);

                Console.WriteLine("beta:" + BETAS[ii]);
                Console.WriteLine("ratio:" + ratio);

                if (ratio > maxRatio)
                {
                    maxRatio = ratio;
                    weights = tempWeights[ii];
                    maxBeta = BETAS[ii];
                }
            }

            Console.WriteLine("Max ratio is: " + maxRatio);
            Console.WriteLine("The beta which maximizes the ratio: " + maxBeta);

            return maxBeta;

        }


        private void WriteR(double[,] R)
        {
            Console.Write("                        ");
            for (int i = 0; i < trainingDatabase.TotalClassCount; i++)
            {
                Console.Write("{0,20}", trainingDatabase[i][0].gestureType);
            }
            Console.WriteLine();

            for (int i = 0; i < R.GetLength(0); i++)
            {
                Console.Write("{0,20}", trainingDatabase[i][0].gestureType);
                for (int j = 0; j < R.GetLength(1); j++)
                {
                    Console.Write(String.Format("{0,20:0.0}", R[i, j]));
                }
                Console.WriteLine();
            }
        }

        private double GetVarianceRatio(double[,] R)
        {
            WriteR(R);

            double meanL = 0;
            double meanU = 0;

            double lCount = ((Math.Pow(R.GetLength(0), 2) - R.GetLength(0)) / 2);
            double uCount = R.GetLength(0);

            for (int i = 0; i < R.GetLength(0); i++)
                for (int j = 0; j < i; j++)
                    meanL += R[i, j];

            meanL = meanL / lCount;           
            
            for (int i = 0; i < R.GetLength(0); i++)
                meanU += R[i, i];

            meanU = meanU / uCount;

            return meanL / meanU;
        }

        public Dictionary<GestureType, Dictionary<JointType, double>> GetWeights(double BETA)
        {
            var activities = Common.GetTotalDisplacements(this.trainingDatabase);

            NoiseFiltering(activities);

            for (int i = 0; i < activities.Count; i++)
            {
                Dictionary<JointType, double> temp = activities[activities.Keys.ElementAt(i)];

                for (int j = 0; j < temp.Count; j++)
                {
                    temp[temp.Keys.ElementAt(j)] = CalculateFinalWeight(temp[temp.Keys.ElementAt(j)], BETA);
                }
            }

            foreach (GestureType g in activities.Keys)
            {
                Common.NormalizeWeights(activities[g]);
            }

            return activities;
        }

        public void WriteWeightsToFile(Dictionary<GestureType, Dictionary<JointType, double>> weights, string fileName)
        {
            using (TextWriter writer = File.CreateText(fileName))
            {
                for (int i = 0; i < weights.Count; i++)
                {
                    writer.WriteLine(weights.Keys.ElementAt(i));
                    Dictionary<JointType, double> temp = weights[weights.Keys.ElementAt(i)];

                    for (int j = 0; j < temp.Count; j++)
                    {
                        writer.Write(temp.Keys.ElementAt(j) + "  ->  ");
                        writer.WriteLine(temp.Values.ElementAt(j).ToString(CultureInfo.InvariantCulture));
                    }
                }
            }
        }

        public double CalculateFinalWeight(double d, double BETA)
        {
            return (1 - Math.Exp(-1 * BETA * d));
        }

        public GestureType Recognize(Gesture g)
        {
            Common.CenterizeGesture(g);
            Common.NormalizeShoulderLength(g);

            double minCost = Double.PositiveInfinity;
            GestureType minCostType = GestureType.UnknownGestureType;

            for (int i = 0; i < trainingDatabase.TotalClassCount; i++)
            {
                double[] costs = new double[trainingDatabase[i].Count];

                for (int j = 0; j < costs.Length; j++)
                {
                    costs[j] = Common.DTW(g, trainingDatabase[i][j], null, weights[trainingDatabase[i][0].gestureType]);
                }

                Array.Sort(costs);
                double medianCost = costs[0];

                if (medianCost < minCost)
                {
                    minCost = medianCost;
                    minCostType = trainingDatabase[i][0].gestureType;
                }
            }

            return minCostType;
        }

        

        public void NoiseFiltering(Dictionary<GestureType, Dictionary<JointType, double>> totalDisplacements)
        {
            for (int i = 0; i < totalDisplacements.Count; i++)
            {
                Dictionary<JointType, double> weights = totalDisplacements[totalDisplacements.Keys.ElementAt(i)];

                for (int j = 0; j < weights.Count; j++)
                {
                    weights[weights.Keys.ElementAt(j)] = NoiseFiltering(weights.Values.ElementAt(j));
                }
            }
        }

        public double NoiseFiltering(double weight)
        {
            double T1 = 0.1;
            double T2 = 0.28;

            if (weight < T1)
            {
                return 0.05;
            }
            else if (weight < T2)
            {
                return (weight - T1) / (T2 - T1);
            }
            else
            {
                return 0.95;
            }
        }
    }
}
